#include "inode.h"

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include "filehandle.h"
#include "folder.h"


fs_ret_t fs_newInode(FileSystemHandle_t *fsh,inodeid_t* address, bool isFolder, bool needAllocation){
	inode_t toAdd = {1,false,FS_DEFAULT_RIGHTS,0,0,'X',{0},'I',0,0,0,'E'/*, zeroes*/};
	byte_t* block = NULL;
	blockid_t whereItIs;
	fs_ret_t ret = RET_NO_PROBLEM;
	toAdd.isFolder = isFolder;
	toAdd.lastAccess = time(NULL);
	if(needAllocation){
		ret = fs_bookAnyEmptyBlock(fsh,&whereItIs);
		if(ret) return ret;
		DEBUG_PRINT("+ %d (inode)\n",whereItIs);
	}else{
		whereItIs = *address;
		DEBUG_PRINT("No allocation at %d\n",whereItIs);
	}
	// Super method to get the inodeid.
	*address = (inodeid_t)whereItIs;
	block = malloc(fsh->fsd.blockSize);
	if(!block) return ERR_OUT_OF_MEMORY;
	memcpy(block,&toAdd,sizeof(inode_t));
	ret = fs_setBlockToHdi(fsh->hdi,*address,block);
	free(block);
	return ret;
}

/// This recursive (indirectionLevel recursion depth, size%blockSize calls)
/// function delete the blocks given.
/// \todo Derecursive it. If deemed necessary.
static fs_ret_t fs_deleteIndirect(
	FileSystemHandle_t *fsh,
	uint16_t indirectionLevel,
	blockid_t startBlock,
	filepos_t *currentBlocksLeft,
	byte_t* blocks)
{
	fs_ret_t ret = RET_NO_PROBLEM;
	uint16_t i;
	bool first = false;
	if(!*currentBlocksLeft) return ret;
	if(!blocks){// Allocating the indirectionLevel blocks, all at once.
		first = true;
		blocks = malloc(fsh->fsd.blockSize*indirectionLevel);
		if(!blocks){
			ret = ERR_OUT_OF_MEMORY;
			goto cleanup;
		}
	}
	if(indirectionLevel!=0){// Removing all the contained blocks.
		ret = fs_getBlockFromHdi(fsh->hdi,startBlock,blocks);
		if(ret) goto cleanup;
		for(i=0;i<fsh->fsd.blockSize/4;++i){
			if(*currentBlocksLeft){
				ret = fs_deleteIndirect(fsh, indirectionLevel-1,
					                    ((blockid_t*)blocks)[i],
										currentBlocksLeft,
										blocks+fsh->fsd.blockSize);
				if(ret) goto cleanup;
			}else{
				break;
			}
		}
	}else{
		--(*currentBlocksLeft);
	}
	ret = fs_freeBlock(fsh,startBlock);// Removing self.
	DEBUG_PRINT("- %d i %d\n",startBlock,indirectionLevel);
	cleanup:
	if(first) free(blocks);
	return ret;
}

fs_ret_t fs_deleteFileBlocks(FileSystemHandle_t *fsh, inode_t *inode){
	fs_ret_t ret = RET_NO_PROBLEM;
	uint16_t i;
	// This calcul doesn't work for O, so handling it after.
	filepos_t currentBlocksLeft = (inode->fileSize-1)/fsh->fsd.blockSize+1;
	if(!currentBlocksLeft||!inode->fileSize) return ret;
	for(i=0;i<FS_INODE_STATIC_BLOCKS_COUNT;++i){
		ret = fs_deleteIndirect(fsh, 0, inode->blocksfirsts[i],&currentBlocksLeft, NULL);
		if(ret) return ret;
		if(!currentBlocksLeft) return ret;
	}
	ret = fs_deleteIndirect(fsh, 1, inode->simpleIndirect,&currentBlocksLeft, NULL);
	if(ret||!currentBlocksLeft) return ret;
	ret = fs_deleteIndirect(fsh, 2, inode->doubleIndirect,&currentBlocksLeft, NULL);
	if(ret||!currentBlocksLeft) return ret;
	ret = fs_deleteIndirect(fsh, 3, inode->tripleIndirect,&currentBlocksLeft, NULL);
	if(ret||!currentBlocksLeft) return ret;
	return ERR_CANT_HAPPEN;
}

fs_ret_t fs_removeInodeRef(FileSystemHandle_t *fsh,inodeid_t inodeAddress){
	byte_t *block = malloc(fsh->fsd.blockSize);
	inode_t *inode = (inode_t*)block;// For convenience.
	fs_ret_t ret;
	if(!block) return ERR_OUT_OF_MEMORY;
	if(fs_inodeIsOpened(fsh, inodeAddress, NULL)){
		ret = ERR_ALREADY_USED;
		goto cleanup;
	}
	if(inode->isFolder){
		ret = fs_deleteFolder(fsh, inodeAddress);
		if(ret) goto cleanup;
	}
	ret = fs_getBlockFromHdi(fsh->hdi, inodeAddress, block);
	if(ret) goto cleanup;
	inode->refCount -= 1;
	ret = fs_setBlockToHdi(fsh->hdi, inodeAddress, block);
	if(ret) goto cleanup;
	if(inode->refCount==0){
		DEBUG_PRINT("Removing %d (size %d).\n",inodeAddress,(int32_t)(inode->fileSize));
		ret = fs_deleteFileBlocks(fsh, inode);
		if(ret) goto cleanup;
		ret = fs_freeBlock(fsh,inodeAddress);
		DEBUG_PRINT("- %d (inode)\n",inodeAddress);
	}
	cleanup:
	if(ret){
		// A removal failed. The file cannot be deleted.
		// We are in deep trouble. The only thing we can
		// do is ignore this and leave the blocks to set.
		// They won't ever be removed, but there is no choice.
		WARNING_PRINT("Disk is corrupted, could not remove a file or folder completely\n");
	}
	free(block);
	return ret;
}

fs_ret_t fs_getInode(FileSystemHandle_t *fsh,inodeid_t address,inode_t* inodeContent){
	byte_t *block = malloc(fsh->fsd.blockSize);
	blockid_t blockToOpen = (blockid_t)address;
	fs_ret_t getBlockRet;
	if(!block) return ERR_OUT_OF_MEMORY;
	getBlockRet = fs_getBlockFromHdi(fsh->hdi,blockToOpen, block);
	if(getBlockRet){
		free(block);
		return getBlockRet;
	}
	memcpy(inodeContent,block,sizeof(inode_t));
	free(block);
	return RET_NO_PROBLEM;
}

fs_ret_t fs_d_printFileInfo(FileSystemHandle_t *fsh,inodeid_t address){
	inode_t inode;
	char rights[10];
	int8_t i;
	fs_ret_t ret = fs_getInode(fsh,address,&inode);
	if(ret){
		printf("Problem happend. Can't display inode %u info.\n",address);
		return ret;
	}
	printf("Inode n %d at block %d.\n",address,(blockid_t)address);
	printf("Has %u items pointing on it.\n",inode.refCount);
	for(i=0;i<3;++i){
		if(inode.rights&(4<<((2-i)*3))){
			rights[i*3+0] = 'r';
		}else{
			rights[i*3+0] = '-';
		}
		if(inode.rights&(2<<((2-i)*3))){
			rights[i*3+1] = 'w';
		}else{
			rights[i*3+1] = '-';
		}
		if(inode.rights&(1<<((2-i)*3))){
			rights[i*3+2] = 'x';
		}else{
			rights[i*3+2] = '-';
		}
	}
	rights[9] = '\0';
	printf("Rights : %s , or %u.\n",rights, inode.rights);
	if(inode.isFolder){
		printf("This is a directory.\n");
		/// \todo Print more stuff.
	}else{
		printf("This is a file.\n");
		printf("File size : %llu\n",(long long unsigned)inode.fileSize);
	}
	return ret;
}


